package thread;

/**
 * 【https://blog.csdn.net/weixin_41951205/article/details/123193025】
 * 测试结果显示：【忽略代码出现的空指针问题】。
// *      【final j】总是正确的，但是【普通变量 i】有可能会读取到它的【默认值，0】。
 *      使用【benchMark】测试工具显示，使用【final】的不会出现问题，但是使用【非final】的会出现线程安全的问题。
 */

/**
 * 解释：解释上面的情况。
 *  1：final域为基本数据类型。
 *      1：写final域的重排序规则：
 *          【写final域的重排序规则禁止把final域的写重排序到构造函数之外】。【也就是final的写，只能在构造方法中完成，并且不能重排序】。
 *          包含下面两部分：1：【JMM禁止编译器把final域的写重排序到构造函数之外】。
 *                        2：【编译器会在final域的写之后，构造函数return之前，插入一个StoreStore屏障。】这个屏障，【禁止处理器把final域的写重排到构造函数之外】。
 *                        解析【writer()】方法见图：【final域的写重排序.png】。简单来说就是。【final变量j的初始化一定在构造方法中完成，（写写屏障保证的）】。
 *                          但是【普通变量 i的初始化，可能会重排序到构造器外面，（这时读到的变量就是，默认值 0）】。
 *                          【i和j变量没有数据依赖性，不满足happens-before原则，所以出现了i变量被重排序到构造方法外的情况。（如果具有依赖性，那么i变量也不会被重排序）】。
 *          小结：【这个规则保证了：别的线程在调用这个对象之前，对象的final域已经被正确初始化过了，而普通域不具有这个保障。】
 *
 *      2：读final域的重排序规则：这是专门针对于处理器，目前有一种处理器会出现这种情况（比如alpha处理器（这是一种处理器指令集架构））。
 *          【该规则是：在一个线程中，初次【读对象的引用】与【初次读该对象的final域变量】】这两个操作，【JMM禁止某些处理器（alpha）重排序这两个操作】。【它会加LoadLoad屏障】。
 *          【java为了整合所有的处理器，所做的额外的考虑】。在很多处理器中【都是默认的，不需要加这个屏障】，但是【有部分处理器，会将上面的两种操作重排序】。
 *          解析【read()】方法，见图：【final域的读重排序.png】。简单来说就是：【final变量j的读取会被【读读屏障】限制在，读取引用之后，】。
 *              但是【普通变量i的读取，可能会被重排序到引用读取之前】。（这个规则好反常识啊，外面的大对象都没有读取到，怎么能读里面的变量呢？）
 *              所以这个规则只针对于部分处理器，对于其他处理器来说，本身就不需要这个【读读屏障】来限制。
 *          小结：【这个规则保证了：在读一个对象的final域之前，一定会先读包含这个final域的对象的引用】。
 *
 *  2：final域为引用数据类型。见下类【FinalReferenceExample.java】
 *      1：写final域的重排序规则：
 *      2：读final域的重排序规则：
 */
public class FinalExample {
    int i; // 普通变量
    final int j; // final变量
    static FinalExample obj;

    public FinalExample() { // 构造函数
        i = 1; // 写普通域
        j = 2; // 写final域
        //这一步：【final变量 j】不会重排序，【普通变量 i】由于和【j】没有数据依赖性，所以会重排序到，构造函数外（有可能）。
        //如果【修改代码： j = i++;】那么【i、j】就有数据依赖性，满足【happens-before原则】，【i也不会被重排序】。
        //因此：j的值总是正确的，但是【i（没有依赖性的）】，的值可能不对。
    }

    public static void writer() { // 写线程A执行
        obj = new FinalExample();
    }

    public static void reader() { // 读线程B执行
        FinalExample object = obj; // 读对象引用
        int a = object.i; // 读普通域
        int b = object.j; // 读final域
        System.out.println(Thread.currentThread().getName()+":"+object.i+":"+object.j);
    }

    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                writer();
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                reader();
            }
        };

        t1.start();
        t2.start();

    }
}

/**
 * final域为引用数据类型的重排序规则：引用类型的重排序具有基本类型的特点，下面的只是一些额外的特点。
 *      1：写final域的重排序规则：
 *          对于引用类型：写final域的重排序规则对编译器和处理器增加了如下约束：
 *              【在构造函数内对一个final引用的对象的成员域的写入，与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序】。
 *              代码【obj = new FinalReferenceExample ();】解析见图：【final引用对象的写重排序.png】
 *              简单来说就是：【第一步】、【二步】、【三步】之间存在明显的调用关系，其中【final】修饰的，【第2、3步，不能重排序】。这是在【第二步后面添加写写屏障保证的】。
 *      2：读final域的重排序规则：
 *          【JMM可以保证】其他【所有的线程】，都可以看到【构造器中的内容】。比如【第0个元素】。【但是如果对这个引用中的内容进行其他的修改。JMM不保证能看到】。
 *          所以需要增加一条：【禁止在【构造函数对一个final修饰的对象的成员域的写入】与随后将【这个被构造的对象的引用赋值给引用变量重排序】。】
 */
class FinalReferenceExample {
    final int[] intArray; // final是引用类型
    static FinalReferenceExample obj;
    public FinalReferenceExample () { // 构造函数
        intArray = new int[1]; // 第一步：给final变量赋值。
        intArray[0] = 1; // 第二步：给final变量中的引用赋值。（它不能被重写在构造函数之外），（并且存在依赖关系，所以位置只能这样）。
    }
    public static void writerOne () { // 写线程A执行
        obj = new FinalReferenceExample (); // 第三步：将这个对象，赋值给obj的引用。这一步不能发生在构造方法之前。（由于final禁止了这种重排序）。
    }
    public static void writerTwo () { // 写线程B执行
        obj.intArray[0] = 2; // 4
    }
    public static void reader () { // 读线程C执行
        if (obj != null) { // 5
            int temp1 = obj.intArray[0]; // 6
        }
    }
}

/**
 * 看一个可能出现线程安全的问题。
 * 下面这段代码可能出现线程安全问题。
 *      一个线程A：执行writer方法。一个线程B：执行reader方法。如果线程A先执行，线程B再执行。可能出现如下情况。
 *      1：线程A：先执行：【第一步、写final域。（这个不会被重排序到构造方法外）】、【第二步：将this赋值给obj】。
 *          线程B：再执行【第三步：判断obj是否为空】、【第四步：获取final i的值】。
 *      2：但是由于【1、2步没有依赖关系，所以可能出现】。线程A【第二步】 --》 线程B【3、4步】  --》线程A【第一步】。
 *          这个就会出现线程安全问题，因为线程A【先把一个没有初始化好的对象返回给线程B使用了】。【这时线程B是不能用的，所以线程B会出现无法预计的错误】。
 *
 */
class FinalReferenceEscapeExample {
    final int i;
    static FinalReferenceEscapeExample obj;
    public FinalReferenceEscapeExample () {
        i = 1; // 1写final域
        obj = this; // 2 this引用在此"逸出"
    }
    public static void writer() {
        new FinalReferenceEscapeExample ();
    }
    public static void reader() {
        if (obj != null) { // 3
            int temp = obj.i; // 4
        }
    }
}


